package registration;

import java.sql.*;
import java.util.*;

import user.*;

import common.Pair;
import common.dal.*;
import common.dal.SQLDataColumn.Constraints;
import common.exceptions.*;
import common.extensions.*;
import common.interfaces.IVoteManager;

class DBVoteManager extends AbstractPersistent implements IVoteManager {
	
	protected DBVoteManager(Transaction t) {
		super(t);
	}
	
	private final static String	CONID	= "conId";
	private final static String	USERID	= "userId";
	private final static String	VOTE	= "vote";
	
	@Override
	protected SQLDataColumn[] getTableColumns() {
		return new SQLDataColumn[] {
				new SQLDataColumn(CONID, SQLDataType.BIGINT,
						EnumSet.of(Constraints.FOREIGN_KEY, Constraints.NOT_NULL, Constraints.INDEX),
						Pair.of(DBConsensusManager.TABLE_NAME, DBConsensusManager.PRIMARY_KEY)),
				new SQLDataColumn(USERID, SQLDataType.VARCHAR_50,
						EnumSet.of(Constraints.FOREIGN_KEY, Constraints.NOT_NULL),
						Pair.of(DBPasswordManager.TABLE_NAME, DBPasswordManager.PRIMARY_KEY)),
				new SQLDataColumn(VOTE, SQLDataType.BOOLEAN, Constraints.NOT_NULL)};
	}
	
	@Override
	protected String getTableName() {
		return "votes";
	}
	
	// checks input arguments (are not null, are valid)
	private void checkArguments(Consensus con, User user) throws SQLException, InvalidUserException,
			InvalidConsensusException {
		checkConsensus(con);
		if (user == null) {
			throw new IllegalArgumentException("user is null");
		}
		try {
			if (RegistrationManager.getNewInstance(t).isUserRegisteredToEvent(user, con.getEvent()) == false) {
				throw new IllegalArgumentException(user + " is not registered to " + con.getEvent());
			}
		} catch (InvalidEventException e) {
			throw new IllegalArgumentException("user " + user + " not registered to event " + con.getEvent());
		}
	}
	
	// checks all parameter of a consensus (is not null and is valid)
	private void checkConsensus(Consensus con) throws SQLException, InvalidConsensusException {
		if (con == null) {
			throw new IllegalArgumentException("con is null");
		}
		try {
			RegistrationManager.getNewInstance(t).getConsensus(con.getId());
		} catch (InvalidKeyException e) {
			throw new InvalidConsensusException(con);
		}
	}
	
	// Gets the where clause to use when searching
	private static Collection<WhereEntry> getWhereClause(Consensus con, User user) {
		return Arrays.asList(new WhereEntry(CONID, WhereOperator.EQUALS, con.getId()), new WhereEntry(USERID,
				WhereOperator.EQUALS, user.getId()));
	}
	
	@Override
	public void vote(Consensus con, User user, boolean vote) throws InvalidConsensusException,
			InvalidUserException, SQLException {
		checkArguments(con, user);
		deleteWhere(getWhereClause(con, user));
		insertInto(new Object[] {con.getId(), user.getId(), vote});
	}
	
	@Override
	public Boolean getConsensusStatus(Consensus con) throws InvalidConsensusException, SQLException {
		checkConsensus(con);
		Collection<Pair<User, Boolean>> allVotes = getAllVotes(con);
		try {
			// size checks
			int numberOfRegisteredUsers = RegistrationManager.getNewInstance(t)
					.getAllRegistrationsToEvent(con.getEvent()).size();
			if (numberOfRegisteredUsers == 0) {
				// no one voted
				return null;
			} else if (numberOfRegisteredUsers == 1) {
				if (allVotes.size() == numberOfRegisteredUsers) {
					// only one person voted
					Boolean firstVote = Extender.first(allVotes).second;
					assert firstVote != null;
					return firstVote;
				} else {
					assert allVotes.isEmpty();
					return null;
				}
			}
			if (allVotes.size() != numberOfRegisteredUsers) {
				assert allVotes.size() < numberOfRegisteredUsers;
				return null;
			}
		} catch (InvalidEventException e) {
			// con already check, so event is ok because of foreign constraint
			throw new AssertionError(e);
		}
		final boolean firstVote = Extender.first(allVotes).second;
		boolean consensusBeenReached = Extender.all(allVotes, new Predicate<Pair<User, Boolean>>() {
			@Override
			public Boolean calculate(Pair<User, Boolean> element) {
				return element.second == firstVote;
			}
		});
		if (consensusBeenReached) {
			return firstVote;
		} else {
			return null;
		}
	}
	
	@Override
	public Collection<Pair<User, Boolean>> getAllVotes(Consensus con) throws InvalidConsensusException,
			SQLException {
		checkConsensus(con);
		try (ResultSet resultSet = selectWhere(new WhereEntry(CONID, WhereOperator.EQUALS, con.getId()))) {
			Collection<Pair<User, Boolean>> result = new LinkedList<>();
			while (resultSet.next()) {
				String userId = resultSet.getString(USERID);
				User user;
				try {
					user = LoginManager.getNewInstance(t).get(userId);
				} catch (InvalidKeyException e) {
					// foreign key constraint
					throw new AssertionError(e);
				}
				result.add(Pair.of(user, resultSet.getBoolean(VOTE)));
			}
			
			return result;
		}
	}
	
	@Override
	public Boolean getVote(Consensus con, User user) throws InvalidConsensusException, InvalidUserException,
			SQLException {
		checkArguments(con, user);
		try (ResultSet resultSet = selectWhere(getWhereClause(con, user))) {
			if (resultSet.first()) {
				return resultSet.getBoolean(VOTE);
			} else {
				return null;
			}
		}
	}
	
	@Override
	public void removeAllVotes(Consensus con) throws SQLException {
		deleteWhere(new WhereEntry(CONID, WhereOperator.EQUALS, con.getId()));
	}
	
	@Override
	public void removeAllVotes(User user) throws SQLException {
		deleteWhere(new WhereEntry(USERID, WhereOperator.EQUALS, user.getId()));
	}
}
